package fr.vahren.sriksal.generation;

import java.util.ArrayList;

public class RiverDropStrategy implements DropStrategy {

	private static final int INERTIA = 1;

	private int maxAlt;
	private int dropFreq;
	private int dropCapacity;
	private ArrayList<Drop> drops;

	private double[][] densityMap;

	private int[][] materialMap;

	public RiverDropStrategy(int dropCapacity, int dropFreq, int maxAlt,
			ArrayList<Drop> drops) {
		this.dropCapacity = dropCapacity;
		this.dropFreq = dropFreq;
		this.maxAlt = maxAlt;
		this.drops = drops;
	}

	@Override
	public void spawnDrops(int[][] map) {
		for (int i = 0; i < dropFreq; i++) {
			Drop drop = new Drop(dropCapacity);
			int x = (int) (Math.random() * map.length);
			int y = (int) (Math.random() * map[0].length);
			if (map[x][y] <= maxAlt) {
				drop.setPosition(new Position(x, y, map[x][y]));
				drops.add(drop);
			}// else pass
		}
	}

	@Override
	public void move(Drop drop, Position nextPosition, int[][] map) {
		// Move the drop!
		drop.setPosition(nextPosition);
	}

	@Override
	public void fillDrop(Drop drop, Position nextPosition, int map[][]) {
		if (nextPosition != null) {
			// Get next position altitude
			int nextAlt = map[nextPosition.getX()][nextPosition.getY()];
			// Get current position altitude
			int x = drop.getPosition().getX();
			int y = drop.getPosition().getY();
			int alt = map[x][y];
			// Fill the drop with the maximum without leaving a hole
			if (!drop.isFull(dropCapacity)) {
				double chance = densityMap[x][y] + 0.25 + (alt - nextAlt)
						* 0.05;
				chance = Math.max(0, chance
						- (materialMap[x][y] > 0 ? 0.25 : 0));
				// If the place is more dense it is more difficult to grab soil
				// Also if the material is still earth (materialMap > 0) it is
				// easier. No less than 0.
				if (Math.random() > chance) {
					int quantity = Math.min(dropCapacity, alt - nextAlt + 1);
					drop.addSoil(quantity);
					// Remove the soil from the map.
					map[x][y] -= quantity;
					// Don't forget to remove here to know if the earth layer is
					// finished.
					materialMap[x][y] -= quantity;
				}
			}
		}
	}

	/**
	 * 
	 * @param drop
	 * @param map
	 * @return null if there is nowhere to go, or the next position of the drop.
	 */
	public Position getNextPosition(Drop drop, int[][] map) {

		int x = drop.getPosition().getX();
		int y = drop.getPosition().getY();
		int h = drop.getPosition().getH();

		Direction direction = getDirection(map, x, y, h);

		switch (direction) {
		case NOWHERE:
			// Nowhere to go upward! Evaporate.
			evaporate(drop, map);
			return null;
		case UP:
			return new Position(x - INERTIA, y, map[x - INERTIA][y]);
		case LEFT:
			return new Position(x, y - INERTIA, map[x][y - INERTIA]);
		case RIGHT:
			return new Position(x, y + INERTIA, map[x][y + INERTIA]);
		case DOWN:
			return new Position(x + INERTIA, y, map[x + INERTIA][y]);
		default:
			break;
		}
		return null;
	}

	private Direction getDirection(int[][] map, int x, int y, int h) {
		float altUp = maxAlt, altLeft = maxAlt, altRight = maxAlt, altDown = maxAlt;
		// Up x-1
		if (x >= INERTIA) {
			altUp = map[x - INERTIA][y];
		}
		// Left y-1
		if (y >= INERTIA) {
			altLeft = map[x][y - INERTIA];
		}
		// Right y+1
		if (y < Generator.WIDTH - INERTIA) {
			altRight = map[x][y + INERTIA];
		}
		// Down x+1
		if (x < Generator.HEIGHT - INERTIA) {
			altDown = map[x + INERTIA][y];
		}

		float[] alts = { altUp, altLeft, altRight, altDown };
		float minAlt = maxAlt;
		Direction direction = null;
		for (int i = 0; i < alts.length; i++) {
			if (alts[i] < minAlt) {
				minAlt = alts[i];
				direction = Direction.values()[i];
			} else if (alts[i] == minAlt) {
				// 0.5 chance to choose this one
				if (Math.random() > 0.5) {
					minAlt = alts[i];
					direction = Direction.values()[i];
				}
			}
		}
		if (minAlt >= h) {
			direction = Direction.NOWHERE;
		}
		return direction;
	}

	private void evaporate(Drop drop, int[][] map) {
		int x = drop.getPosition().getX();
		int y = drop.getPosition().getY();
		// leave the soil
		map[x][y] += drop.getSoil();
		// Add to material cause we drop earth
		materialMap[x][y] += drop.getSoil();
		// Delete from the drops
		drops.remove(drop);
	}

	@Override
	public void init(int[][] map) {
		// Create density map
		densityMap = Generator.generateDensityMap(map.length, map[0].length,
				20, 20);

		materialMap = Generator.generateMaterialMap(map.length, map[0].length,
				5);
	}


	@Override
	public int[][] getTerrainMap() {
		return materialMap;
	}


}
